package main

import (
	"bytes"
	"encoding/binary"
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/panjf2000/gnet"
	"github.com/panjf2000/gnet/pool/goroutine"
	"test.com/src/msgcmd"
	"test.com/src/protocol"
)

type ChatServer struct {
	*gnet.EventServer
	addr       string
	multicore  bool
	async      bool
	codec      gnet.ICodec
	workerPool *goroutine.Pool

	playerMgr PlayerMgr
	roomMgr   RoomMgr
}

func (cs *ChatServer) OnInitComplete(srv gnet.Server) (action gnet.Action) {
	log.Printf("Test codec server is listening on %s (multi-cores: %t, loops: %d)\n",
		srv.Addr.String(), srv.Multicore, srv.NumEventLoop)
	return
}

func (cs *ChatServer) OnOpened(c gnet.Conn) (out []byte, action gnet.Action) {
	player := Player{}
	player.Name = c.RemoteAddr().String()
	player.Session = c
	c.SetContext(player.Name)
	cs.playerMgr.AddPlayer(&player)

	msg, _ := protocol.BuildRoomListMsg(uint32(cs.roomMgr.GetRoomCount()))
	c.AsyncWrite(msg)
	return
}

func (cs *ChatServer) OnClosed(c gnet.Conn, err error) (action gnet.Action) {
	playerName := c.Context().(string)
	player := cs.playerMgr.FindPlayer(playerName)
	if player != nil {
		room := cs.roomMgr.GetRoom(player.RoomID)
		if room != nil {
			room.PlayerOut(playerName)
		}
	}
	cs.playerMgr.RemovePlayer(playerName)
	return
}

func (cs *ChatServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) {

	playerName := c.Context().(string)

	player := cs.playerMgr.FindPlayer(playerName)
	if player != nil {

		cmd, buffer := protocol.DecodeCmd(frame)
		switch cmd {
		case msgcmd.Heatbeat:
			c.AsyncWrite(frame)
		case msgcmd.ChangeNameReq:
			cs.handleChangeName(player, buffer)
		case msgcmd.PlayerInReq:
			cs.handlePlayerIn(player, buffer)
		case msgcmd.ChatMsg:
			cs.handleChat(player, buffer)
		}
	}

	return
}

func (cs *ChatServer) handleChangeName(player *Player, buffer *bytes.Buffer) {
	req := protocol.DecodeChangeNameReq(buffer)
	var rsp []byte

	curPlayer := cs.playerMgr.FindPlayer(*req.Name)
	if curPlayer != nil && curPlayer != player {
		rsp, _ = protocol.BuildChangeNameRsp(false, player.Name, "该名字已被使用")
	} else {
		rsp, _ = protocol.BuildChangeNameRsp(true, *req.Name, "")
		cs.playerMgr.updatePlayerName(player.Name, *req.Name)
		player.Session.SetContext(player.Name)
	}

	player.Session.AsyncWrite(rsp)
}

func (cs *ChatServer) handlePlayerIn(player *Player, buffer *bytes.Buffer) {
	req := protocol.DecodePlayerInReq(buffer)
	room := cs.roomMgr.GetRoom(int(*req.RoomId))
	if room != nil {
		cs.playerOut(player)
		msgs := room.PlayerIn(player)

		msg, _ := protocol.BuildPlayerInRsp(true, uint32(player.RoomID), msgs)
		player.Session.AsyncWrite(msg)
	} else {
		msg, _ := protocol.BuildPlayerInRsp(false, uint32(player.RoomID), nil)
		player.Session.AsyncWrite(msg)
	}
}

func (cs *ChatServer) playerOut(player *Player) {
	room := cs.roomMgr.GetRoom(player.RoomID)
	if room != nil {
		room.PlayerOut(player.Name)
	}
}

func (cs *ChatServer) handleChat(player *Player, buffer *bytes.Buffer) {
	msg := protocol.DecodeChat(buffer)
	room := cs.roomMgr.GetRoom(player.RoomID)
	if room != nil {
		room.handleChat(player, *msg.Txt)
	}
}

func initChatServe(addr string, multicore, async bool, codec gnet.ICodec) {
	var err error
	if codec == nil {
		encoderConfig := gnet.EncoderConfig{
			ByteOrder:                       binary.BigEndian,
			LengthFieldLength:               4,
			LengthAdjustment:                0,
			LengthIncludesLengthFieldLength: false,
		}
		decoderConfig := gnet.DecoderConfig{
			ByteOrder:           binary.BigEndian,
			LengthFieldOffset:   0,
			LengthFieldLength:   4,
			LengthAdjustment:    0,
			InitialBytesToStrip: 4,
		}
		codec = gnet.NewLengthFieldBasedFrameCodec(encoderConfig, decoderConfig)
	}
	cs := &ChatServer{addr: addr, multicore: multicore, async: async, codec: codec, workerPool: goroutine.Default()}
	cs.playerMgr = *NewPlayerMgr()
	cs.roomMgr = *newRoomMgr(5)
	err = gnet.Serve(cs, addr, gnet.WithMulticore(multicore), gnet.WithTCPKeepAlive(time.Minute*5), gnet.WithCodec(codec))
	if err != nil {
		panic(err)
	}
}

func main() {
	var port int
	var multicore bool

	flag.IntVar(&port, "port", 9000, "server port")
	flag.BoolVar(&multicore, "multicore", true, "multicore")
	flag.Parse()
	addr := fmt.Sprintf("tcp://:%d", port)

	initChatServe(addr, multicore, false, nil)
}
